/* 文件管理数据模块, 这里有文件的所有信息
, 用到了JsonUtil.hpp, FileUtil.hpp, Config.hpp */

#ifndef __MY_DATA_MANGER__
#define __MY_DATA_MANGER__
#define USE_DATABASE
#include "Config.hpp"
#include <iostream>
#include <unordered_map>
#include "DataBaseHelper.hpp"
#include <pthread.h>
#include <string>
#include <vector>
#include <mutex>
#include "log.hpp"
extern Log my_log;

namespace cloud
{
struct BackupInfo
{   
    // 传入文件名, 初始化结构体
    BackupInfo(const std::string& fileName)
    {
        init(fileName);
    }

    BackupInfo() 
    {
        biStatus = false;
    }

    // 当前文件的状态是否ok, ok 返回true
    bool isOk() const
    {
        return biStatus;
    }

    bool isPacked;              // 文件是否被压缩（true表示被压缩了）
    size_t fileSz;              // 文件大小
    time_t lastMTime;           // 文件最后一次修改时间
    time_t lastATime;           // 文件最后一次访问时间
    std::string ulFilePath;     // 上传文件存放的路径(这里要精确到哪个路径下哪个文件，如./files/fileName.txt)
    std::string rarFilePath;    // 压缩文件存放的路径(这里要精确到哪个路径下哪个文件，如./rars/fileName.lz)
    std::string urlPath;        // url的资源路径path
    bool biStatus = true;       // 当前文件的状态是否ok
    std::string uuid;           // 唯一标识符, 用于标识文件
private:
    // 构造函数使用该函数
    bool init(const std::string& fileName)
    {
        try {
            cloud::FileUtil fu(fileName);
            if(fu.exits() == false) {
                // 文件不存在, 后面信息没有初始化的必要了, 直接返回。
                // std::cerr << "BackupInfo(std::string& fileName)\nfile not exits!\n";
                biStatus = false;
                return false;
            }
            // 加载配置文件
            cloud::Config* cf = cloud::Config::getInstance();
            std::string ulPath = cf->getUlPath();       // 上传文件存放的文件夹
            std::string rarPath = cf->getRarPath();     // 压缩文件存放的文件夹
            std::string rarSuf = cf->getRarSuf();       // 压缩文件的后缀
            std::string urlPathPre = cf->getDownPre();  // url path前缀
            // 初始化属性
            std::string pureFileName = fu.getFileName();// 不带路径的文件名
            isPacked = false;
            fileSz = fu.getFileSize();
            lastMTime = fu.getLastMTime();
            lastATime = fu.getLastATime();
            ulFilePath = ulPath + pureFileName;
            rarFilePath = rarPath + pureFileName + rarSuf;
            urlPath = urlPathPre + pureFileName;
            uuid = fu.getUuid();
            return true;
        } catch(const std::exception& e) {
            throw;
        }
        return true;
    }
};
// std::unique_ptr<MySQLConnector> BackupInfo::db = new cloud::MySQLConnector(dbHost, dbUser, dbPass, dbNmae);

class DataManager
{
private:
    std::unordered_map<std::string, BackupInfo> _hash;       // 通过key:url来映射BackupInfo
    std::string _backup;                                     // 备份的信息文件的路径
    pthread_rwlock_t _rwlock;
    std::mutex _mtx;
    std::shared_ptr<MySQLConnector> db; // 数据库连接
public:
    DataManager()
    {   
        try {
            // 初始化
            auto cf = cloud::Config::getInstance();
            _backup = cf->getBackups();
            std::string dbHost = cf->getDbHost();
            std::string dbUser = cf->getDbUser();
            std::string dbPass = cf->getDbPasswd();
            std::string dbName = cf->getDbName();
            // 连接数据库
            db = std::make_shared<MySQLConnector>(dbHost, dbUser, dbPass, dbName);
            // 数据库是否ok
            if(!db->connect()) {
                std::cerr << "connect error!\n";
                throw std::runtime_error("connect error!");
            }
            pthread_rwlock_init(&_rwlock, nullptr);
            // 加载配置信息
            loadBackup();
        } catch (...) {
            throw;
        }

    }

    // 将bi添加到哈希表中
    bool insertInfo(const BackupInfo& bi)
    {
        pthread_rwlock_wrlock(&_rwlock);    // 加写锁
        _hash[bi.urlPath] = bi;
        pthread_rwlock_unlock(&_rwlock);
        // strorageBackup();
        return true;
    }

    // 根据bi修改哈希表中的值
    bool updateInfo(const BackupInfo& bi) 
    {
        pthread_rwlock_wrlock(&_rwlock);    // 加写锁
        _hash[bi.urlPath] = bi;
        pthread_rwlock_unlock(&_rwlock);
        // strorageBackup();
        return true;
    }

    // 通过urlPath获得一个文件信息, 放到info中
    bool get1FromURL(const std::string& urlPath, BackupInfo* info)
    {
        pthread_rwlock_rdlock(&_rwlock);    // 加读锁  
        auto it = _hash.find(urlPath);

        if(it == _hash.end()) {
            // 没有找到该urlPath对应的info
            std::cerr << "bool get1FromURL(const std::string& urlPath, BackupInfo* info)\nwarning: no that urlPath\n";
            pthread_rwlock_unlock(&_rwlock);
            return false;
        }

        // 找到了
        *info = it->second;
        pthread_rwlock_unlock(&_rwlock);
        return true;
    }

    // 通过该文件的路径(本地路径)获得文件信息, 放到info中
    bool get1FromPath(const std::string& path, BackupInfo* info)
    {
        pthread_rwlock_rdlock(&_rwlock);    // 加读锁  
        
        // 遍历哈希表, 看有无符合条件的
        for(const auto& [k, v] : _hash) {
            if(v.ulFilePath == path) {
                *info = _hash[k];
                pthread_rwlock_unlock(&_rwlock);
                return true;
            }
        }

        // 全都遍历了一遍还是没找到
        std::cerr << "bool get1FromPath(const std::string& path, BackupInfo* info)\nwarning: no that path file\n";
        pthread_rwlock_unlock(&_rwlock);
        return false;
    }

    // 获取所有文件信息, 放到数组中
    bool getAllInfo(std::vector<BackupInfo>* vBi) 
    {
        pthread_rwlock_rdlock(&_rwlock);    // 加读锁  
        for(const auto& [_, v] : _hash) {
            // 只使用value
            vBi->push_back(v);
        }
        pthread_rwlock_unlock(&_rwlock);
        return true;
    }

    // 保存备份文件, 将所有文件的信息保存下来, TODO: 这里应该是异步的, 不能阻塞主线程, 而且要写一个根据UUID来插入的函数
    bool strorageBackup()  // 这个函数暂且不用
    {
#ifdef USE_DATABASE
        // 获取所有文件数据
        std::vector<BackupInfo> vBi;
        getAllInfo(&vBi);
        std::cout << "strorageBackup() size: " << vBi.size() << std::endl;
        // 插入backup表中
        try {
            for(const auto& e : vBi) {
                char buf[1024];
                sprintf(buf, 
                    "INSERT INTO backups "
                    "VALUES ('%s', %lu, %d, '%s', '%s', '%s', '%s', '%s');", 
                    e.uuid.c_str(),
                    e.fileSz,
                    e.isPacked ? 1 : 0, 
                    std::to_string(e.lastATime).c_str(),
                    std::to_string(e.lastMTime).c_str(),
                    e.rarFilePath.c_str(),
                    e.ulFilePath.c_str(), 
                    e.urlPath.c_str());
                db->beginTransaction();
                if(!db->executeQuery(buf)) {
                    my_log(WARNING, "insert error!, sql: %s, why: %s\n", buf, db->getLastError().c_str());
                    db->rollBack();
                    // throw std::runtime_error("insert error in bool strorageBackup()!");
                }
                db->commit();
            }
            return true;
        } catch (...) {
            throw;
        }
        return true;
#else
        // 1. 获取所有文件数据
        std::vector<BackupInfo> vBi;
        getAllInfo(&vBi);
        std::unique_lock<std::mutex> lck(_mtx);
        // 2. 将所有数据转换成序列化字符串
        Json::Value root;

        for(const auto& e : vBi) {
            // 将item作为root数组的一行元素
            Json::Value item;
            item["isPacked"] = e.isPacked;
            item["fileSz"] = e.fileSz;
            item["lastATime"] = e.lastATime;
            item["lastMTime"] = e.lastMTime;
            item["ulFilePath"] = e.ulFilePath;
            item["rarFilePath"] = e.rarFilePath;
            item["urlPath"] = e.urlPath;
            root.append(item);
        }
        
        std::string str;
        cloud::JsonUtil::serialize(root, &str);
        // 3. 将数据写入文件中
        cloud::FileUtil fu(_backup);
        if(fu.setContent(str) == false) {
            return false;
        }
        return true;
#endif
    }

    // 将bi插入到数据库中
    bool insertToDatabase(const BackupInfo& bi)
    {
        char buf[1024];
        sprintf(buf, 
            "INSERT INTO backups "
            "VALUES ('%s', %lu, %d, '%s', '%s', '%s', '%s', '%s');", 
            bi.uuid.c_str(),
            bi.fileSz,
            bi.isPacked ? 1 : 0, 
            std::to_string(bi.lastATime).c_str(),
            std::to_string(bi.lastMTime).c_str(),
            bi.rarFilePath.c_str(),
            bi.ulFilePath.c_str(), 
            bi.urlPath.c_str());
        db->beginTransaction();
        if(!db->executeQuery(buf)) {
            my_log(WARNING, "insert error! sql: %s, why: %s\n", buf, db->getLastError().c_str());
            db->rollBack();
            // throw std::runtime_error("insert error in bool strorageBackup()!");
        }
        db->commit();
        return true;
    }

    // 修改bi的状态, 将压缩位设置为true(1)
    bool setPackedInDatabase(const BackupInfo& bi)
    {
        char buf[1024];
        sprintf(buf, "update backups set isPacked = 1 where uuid = '%s';", bi.uuid.c_str());
        db->beginTransaction();
        if(!db->executeQuery(buf)) {
            my_log(WARNING, "update error! sql: %s, why: %s\n", buf, db->getLastError().c_str());
            db->rollBack();
            // throw std::runtime_error("update error in bool setPackedInDatabase()!");
        }
        db->commit();
    }

    // 加载配置文件, 将其放到_hash中
    bool loadBackup()
    {
#ifdef USE_DATABASE
        std::string sql = "select * from backups";
        db->executeQuery(sql);
        auto res = db->getResults();
        for(auto& row : res) {
            // 将查询的数据放到_hash中
            BackupInfo info;
            info.uuid = row["UUID"];
            info.fileSz = std::stoul(row["fileSz"]);
            info.isPacked = row["isPacked"] == "1";
            info.lastATime = std::stoul(row["lastATime"]);
            info.lastMTime = std::stoul(row["lastMTime"]);
            info.ulFilePath = row["ulFilePath"];
            info.rarFilePath = row["rarFilePath"];
            info.urlPath = row["urlPath"];
            insertInfo(info);
        }
        return true;
#else
        // 1. 打开文件, 读取文件内容
        cloud::FileUtil fu(_backup);

        if(fu.exits() == false) {
            // 没有该文件, 没有继续往下走的必要了, 返回
            // std::cerr << "bool loadBackup()\nbackup file not exits()!\n";
            return false;
        }

        std::string content;
        fu.getFullContent(&content);

        if(content.empty() == true)    return true;

        // 2. 得到反序列化字符串
        Json::Value root;
        cloud::JsonUtil::unserialize(content, &root);

        // 3. 将配置信息放到_hash中
        for(int i = 0; i < root.size(); ++i) {
            BackupInfo info;
            info.isPacked = root[i]["isPacked"].asBool();
            info.fileSz = root[i]["fileSz"].asInt64();
            info.lastMTime = root[i]["lastMTime"].asInt64();
            info.lastATime = root[i]["lastATime"].asInt64();
            info.ulFilePath = root[i]["ulFilePath"].asString();
            info.rarFilePath = root[i]["rarFilePath"].asString();
            info.urlPath = root[i]["urlPath"].asString();
            insertInfo(info);
        }

        return true;
#endif
    }
};
}
#endif